﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;

namespace Execute
{
	public class PortableExecutable
	{
		const int offset_to_header = 0x3C;
		/// <summary>
		/// PE\0\0
		/// </summary>
		const int signature = 0x4550;

		public enum CpuType : ushort
		{
			i386 = 0x014C,
		}


		#region Поток данных
		public PortableExecutable(string filePath)
		{
			_data = new MemoryStream(File.ReadAllBytes(filePath));
			_reader = new BinaryReader(_data);
		}

		Stream _data;
		BinaryReader _reader;

		ImageNtHeaders? _ntHeaders;
		int _sectionHeaders;
		public ImageNtHeaders Header
		{
			get
			{
				if (!_ntHeaders.HasValue)
				{
					_data.Position = 0;
					var dosHeader = _data.ReadStruct<DosHeader>();
					_data.Position = dosHeader.NewHeaderOffset;
					var ntHeaders = _data.ReadStruct<ImageNtHeaders>();
					_sectionHeaders = (int)_data.Position;
					_ntHeaders = ntHeaders;
				}

				return _ntHeaders.Value;
			}
		}

		public ImageSectionHeader this[int index]
		{
			get
			{
				if (!_ntHeaders.HasValue) _ntHeaders = Header;
				_data.Position = _sectionHeaders + index * Marshal.SizeOf(typeof(ImageSectionHeader));
				var result = _data.ReadStruct<ImageSectionHeader>();
				if (result.Characteristics == 0)
					throw new IndexOutOfRangeException();
				return result;
			}
		}
		#endregion Поток данных

		#region PE loader
		IntPtr @base;

		public void Load()
		{
			if (@base != IntPtr.Zero)
				throw new InvalidOperationException("Portable Executable has already been loaded");

			@base = VirtualMemory.Alloc(new IntPtr(Header.OptionalHeader.ImageBase), Header.OptionalHeader.SizeOfImage, VirtualMemory.AllocationType.Reserve, VirtualMemory.Protection.NoAccess);
			VirtualMemory.Alloc(@base, Header.OptionalHeader.SizeOfHeaders, VirtualMemory.AllocationType.Commit, VirtualMemory.Protection.RW);

			byte[] headers = new byte[Header.OptionalHeader.SizeOfHeaders];
			_data.Position = 0;
			_data.Read(headers, 0, headers.Length);

			Marshal.Copy(headers, 0, @base, headers.Length);
			VirtualMemory.Protection protection;
			VirtualMemory.Protect(@base, Header.OptionalHeader.SizeOfHeaders, VirtualMemory.Protection.ReadOnly, out protection);

			throw new NotImplementedException();
		}

		#region WinAPI
		class VirtualMemory
		{
			[Flags]
			public enum AllocationType: int
			{
				/// <summary>
				/// Allocates physical storage in memory or in the paging file on disk for the specified reserved memory pages. The function initializes the memory to zero. 
				/// </summary>
				Commit			= 0x1000,
				/// <summary>
				/// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory or in the paging file on disk.
				/// </summary>
				Reserve			= 0x2000,
				/// <summary>
				/// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages should not be read from or written to the paging file. However, the memory block will be used again later, so it should not be decommitted. This value cannot be used with any other value. 
				/// </summary>
				Reset			= 0x80000,


				LargePages		= 0x20000000,
				/// <summary>
				/// Allocates physical memory with read-write access. This value is solely for use with Address Windowing Extensions (AWE) memory.
				/// </summary>
				/// <remarks>This value must be used with MEM_RESERVE and no other values.</remarks>
				Physical		= 0x400000,
				/// <summary>
				/// Allocates memory at the highest possible address.
				/// </summary>
				TopDown			= 0x100000,
				/// <summary>
				/// Causes the system to track pages that are written to in the allocated region. If you specify this value, you must also specify MEM_RESERVE.
				/// </summary>
				WriteWatch		= 0x200000,
			}

			[Flags]
			public enum Protection
			{
				Execute			= 0x10,
				ExecuteR		= 0x20,
				ExecuteRW		= 0x40,
				/// <summary>
				/// Enables execute, read, and write access to the committed region of image file code pages. The pages are shared read-on-write and copy-on-write.
				/// </summary>
				ExecuteWriteCopy= 0x80,

				NoAccess		= 0x01,
				ReadOnly		= 0x02,
				RW				= 0x04,
				WriteCopy		= 0x08,

				Guard			= 0x100,
				NoCache			= 0x200,

				/// <summary>
				/// Enables write-combined memory accesses. When enabled, the processor caches memory write requests to optimize performance. Thus, if two requests are made to write to the same memory address, only the more recent write may occur.
				/// </summary>
				WriteCombine	= 0x400,
			}

			[DllImport("kernel32.dll", EntryPoint="VirtualAlloc")]
			public static extern IntPtr Alloc(IntPtr address, uint size, AllocationType type, Protection protection);

			[DllImport("kernel32.dll", EntryPoint="VirtualProtect")]
			public static extern bool Protect(IntPtr address, uint size, Protection value, out Protection old);
		}
		#endregion WinAPI
		#endregion PE loader
	}

	[Serializable]
	[StructLayout(LayoutKind.Sequential)]
	public unsafe struct DosHeader
	{
		/// <summary>
		/// Magic number
		/// </summary>
		public ushort e_magic;
		/// <summary>
		/// Bytes on last page of file
		/// </summary>
		public ushort e_cblp;
		/// <summary>
		/// Pages in file
		/// </summary>
		public ushort e_cp;
		/// <summary>
		/// Relocations
		/// </summary>
		public ushort e_crlc;
		/// <summary>
		/// Size of header in paragraphs
		/// </summary>
		public ushort e_cparhdr;
		/// <summary>
		/// Minimum extra paragraphs needed
		/// </summary>
		public ushort e_minalloc;
		/// <summary>
		/// Maximum extra paragraphs needed
		/// </summary>
		public ushort e_maxalloc;
		/// <summary>
		/// Initial (relative) SS value
		/// </summary>
		public ushort e_ss;
		/// <summary>
		/// Initial SP value
		/// </summary>
		public ushort e_sp;
		/// <summary>
		/// Checksum
		/// </summary>
		public ushort e_csum;
		/// <summary>
		/// Initial IP value
		/// </summary>
		public ushort e_ip;
		/// <summary>
		/// Initial (relative) CS value
		/// </summary>
		public ushort e_cs;
		/// <summary>
		/// File address of relocation table
		/// </summary>
		public ushort e_lfarlc;
		/// <summary>
		/// Overlay number
		/// </summary>
		public ushort e_ovno;
		/// <summary>
		/// Reserved words
		/// </summary>
		public fixed ushort e_res[4];
		/// <summary>
		/// OEM identifier (for e_oeminfo)
		/// </summary>
		public ushort e_oemid;
		/// <summary>
		/// OEM information; e_oemid specific
		/// </summary>
		public ushort e_oeminfo;
		/// <summary>
		/// Reserved words
		/// </summary>
		public fixed ushort e_res2[10];
		/// <summary>
		/// File address of the new exe header
		/// </summary>
		public int NewHeaderOffset;
	}

	[Serializable]
	[StructLayout(LayoutKind.Sequential)]
	public struct ImageNtHeaders
	{
		public int Signature;

		public ImageFileHeader FileHeader;
		public ImageOptionalHeader32 OptionalHeader;
	}

	[Serializable]
	[StructLayout(LayoutKind.Sequential)]
	public struct ImageFileHeader
	{
		public PortableExecutable.CpuType Machine;
		public ushort NumberOfSections;
		public int TimeDateStamp;
		public int PointerToSymbolTable;
		public int NumberOfSymbols;
		public ushort SizeOfOptionalHeader;
		public ushort Characteristics;
	}

	[Serializable]
	[StructLayout(LayoutKind.Sequential)]
	public struct ImageDataDirectory
	{
		public uint VirtualAddress;
		public uint Size;
	}

	[Serializable]
	[StructLayout(LayoutKind.Sequential)]
	public unsafe struct ImageOptionalHeader32
	{
		/* Standard fields */

		public ushort Magic;
		public byte MajorLinkerVersion;
		public byte MinorLinkerVersion;
		public uint SizeOfCode;
		public uint SizeOfInitializedData;
		public uint SizeOfUninitializedData;
		public uint AddressOfEntryPoint;
		public uint BaseOfCode;
		public uint BaseOfData;

		/* NT additional fields */

		public uint ImageBase;
		public uint SectionAlignment;
		public uint FileAlignment;
		public ushort MajorOperatingSystemVersion;
		public ushort MinorOperatingSystemVersion;
		public ushort MajorImageVersion;
		public ushort MinorImageVersion;
		public ushort MajorSubsystemVersion;
		public ushort MinorSubsystemVersion;
		public uint Win32VersionValue;
		public uint SizeOfImage;
		public uint SizeOfHeaders;
		public uint CheckSum;
		public ushort Subsystem;
		public ushort DllCharacteristics;
		public uint SizeOfStackReserve;
		public uint SizeOfStackCommit;
		public uint SizeOfHeapReserve;
		public uint SizeOfHeapCommit;
		public uint LoaderFlags;
		public uint NumberOfRvaAndSizes;
		public fixed uint DataDirectoryX[32];
		public unsafe ImageDataDirectory[] DataDirectories
		{
			get
			{
				ImageDataDirectory[] result = new ImageDataDirectory[16];
				fixed(uint* datas = DataDirectoryX)
				for (int i = 0; i < 16; i++)
					result[i] = new ImageDataDirectory {
						VirtualAddress = datas[i * 2],
						Size = datas[i * 2 + 1]
					};

				return result;
			}
		}
		public ImageDataDirectory this[DataDirectory directory]
		{
			get
			{
				int index = (int)directory;
				fixed (uint* datas = DataDirectoryX)
					return new ImageDataDirectory {
						VirtualAddress = datas[index * 2],
						Size = datas[index * 2 + 1],
					};
			}
		}

		public enum DataDirectory
		{
			Export		= 0,
			Import		= 1,
			Resource	= 2,
			Exception	= 3,
			Security	= 4,
			/// <summary>
			/// Base relocation table.
			/// </summary>
			Relocations	= 5,
			Debug		= 6,
			/// <summary>
			/// Architecture specific data.
			/// </summary>
			Architecture= 7,
			/// <summary>
			/// RVA of GP (what is it?)
			/// </summary>
			GlobalPTR	= 8,
			TLS			= 9,
			LoadConfig	= 10,
			BoundImport	= 11,
			/// <summary>
			/// Import address table.
			/// </summary>
			ImportAddrs	= 12,
			DelayImport	= 13,
			ComDescr	= 14,
		}
	}

	[Serializable, StructLayout(LayoutKind.Sequential)]
	public unsafe struct ImageSectionHeader
	{
		public fixed byte name[8];
		public uint PhysicalAddressOrVirtualSize;

		public uint VirtualAddress;
		public uint SizeOfRawData;
		public uint PointerToRawData;
		public uint PointerToRelocations;
		public uint PointerToLinenumbers;
		public ushort NumberOfRelocations;
		public ushort NumberOfLinenumbers;
		public uint Characteristics;

		public string Name
		{
			get
			{
				byte[] data = new byte[9];
				int length = 8;
				fixed (byte* secname = name)
				{
					for (int i = 0; i < 8; i++)
					{
						data[i] = secname[i];
						if (data[i] == 0)
						{
							length = i;
							break;
						}
					}
				}

				return Encoding.ASCII.GetString(data, 0, length);
			}
		}
	}

	public static class StreamExtensions
	{
		public static T ReadStruct<T>(this Stream source)
			where T: struct
		{
			int size = Marshal.SizeOf(typeof(T));
			byte[] data = new byte[size];

			if (source.Read(data, 0, size)!= size)
				throw new IOException();

			IntPtr storage = Marshal.AllocHGlobal(size);
			try
			{
				Marshal.Copy(data, 0, storage, size);
				return (T)Marshal.PtrToStructure(storage, typeof(T));
			} finally
			{
				Marshal.FreeHGlobal(storage);
			}
		}
	}
}
